#include <assert.h>
#include <ctype.h>
#include <locale.h>
#include <errno.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <setjmp.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

/*
Failure to de-allocate memory using free leads to a buildup of non-reusable memory, 
which is no longer used by the program;

this is called a memory leak. Memory leaks waste memory resources and can lead to allocation failures.
*/


/*
Logical errors

All allocations must follow the same pattern:
1. Allocation using malloc (or calloc)
2. Usage to store data
3. De-allocation using free


Failure to adhere to this pattern, such as using memory after a call to free (dangling pointer) or before a call to
malloc (wild pointer), calling free twice ("double free"), etc., usually causes a segmentation fault and results in a
crash of the program.
*/


void creating_pointers_to_stack_variables(void)
{
    printf("***** creating_pointers_to_stack_variables *****\n");
/*
Creating a pointer does not extend the life of the variable being pointed to. For example:
    int* myFunction()
    {
    int x = 10;
    return &x;
    }

Here, x has automatic storage duration (commonly known as stack allocation). 

Because it is allocated on the stack, its lifetime is only as long as myFunction is executing; 

after myFunction has exited, the variable x is destroyed. 

This function gets the address of x (using &x), and returns it to the caller, leaving the caller with a pointer to a nonexistent variable.

Attempting to access this variable will then invoke undefined behavior.
*/

// Most compilers don't actually clear a stack frame after the function exits, 

// thus dereferencing the returned pointer often gives you the expected data. 

// When another function is called however, the memory being pointed to may be overwritten, 
// and it appears that the data being pointed to has been corrupted
}

int* variable_allocated_on_the_stack(void)
{
    // x lifetime is only as long as Function is executing
    //  after myFunction has exited, the variable x is destroyed
    int x = 10;
    // warning: function returns address of local variable [-Wreturn-local-addr]
    return x;
}

/*
To resolve this,
1. either malloc the storage for the variable to be returned, and return a pointer to the newly created storage, 
2. or require that a valid pointer is passed in to the function instead of returning one,
*/

int *variable_allocated_with_malloc(void)
{
    printf("***** variable_allocated_with_malloc *****\n");
     int *x = malloc(sizeof *x);
    if ( x == NULL )
    {
        /* Something went wrong */
        return NULL;
    }
    *x = 10;
    return x;
}

void pointer_variable_passed_into_function(int *x)
{
    printf("***** pointer_variable_passed_into_function *****\n");
    /* NB: calling this function with an invalid or null pointer
    causes undefined behaviour. */
    *x = 10;
}

void main(void)
{
    // int *variable_on_stack = variable_allocated_on_the_stack();
    // printf("The value set by variable_allocated_on_the_stack() is %i\n", *variable_on_stack);

    {
        /* Use solution1() */
        int *variable_on_stack = variable_allocated_with_malloc();
        printf("The value set by variable_allocated_with_malloc() is %i\n", *variable_on_stack);
        free(variable_on_stack); /* Tidy up */
    }

    {
        /* Use solution2() */
        int y;
        pointer_variable_passed_into_function(&y);
        printf("The value set by pointer_variable_passed_into_function() is %i\n", y);
        system("pause");
    }
}